package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/elastic/go-elasticsearch/v8/esapi"
	"github.com/elastic/go-elasticsearch/v8/esutil"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"mxshop_srvs/goods_srv/global"
	"mxshop_srvs/goods_srv/model"
	"mxshop_srvs/goods_srv/proto"
	"strconv"
)

/*
做业务逻辑处理 基本是改动这里
*/

type GoodsServer struct {
	proto.UnimplementedGoodsServer
}

func ModelToResponse(goods model.Goods) proto.GoodsInfoResponse {
	goodsInfoResponse := proto.GoodsInfoResponse{
		Id:              goods.ID,
		CategoryId:      goods.CategoryID,
		Name:            goods.Name,
		GoodsSn:         goods.GoodsSn,
		ClickNum:        goods.ClickNum,
		SoldNum:         goods.SoldNum,
		FavNum:          goods.FavNum,
		MarketPrice:     goods.MarketPrice,
		ShopPrice:       goods.ShopPrice,
		GoodsBrief:      goods.GoodsBrief,
		ShipFree:        goods.ShipFree,
		GoodsFrontImage: goods.GoodsFrontImage,
		IsNew:           goods.IsNew,
		IsHot:           goods.IsHot,
		OnSale:          goods.OnSale,
		DescImages:      goods.DescImages,
		Images:          goods.Images,
		Category: &proto.CategoryBriefInfoResponse{
			Id:   goods.Category.ID,
			Name: goods.Category.Name,
		},
		Brand: &proto.BrandInfoResponse{
			Id:   goods.Brands.ID,
			Name: goods.Brands.Name,
			Logo: goods.Brands.Logo,
		},
	}
	return goodsInfoResponse
}

func (g *GoodsServer) GoodsList(ctx context.Context, req *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {
	// 使用 es 的目的是搜索出商品的 id 来, 通过 id 拿到具体的字段信息是通过 mysql 来完成
	// 我们使用 es 是用来做搜索的, 是否应该将所有的 mysql 字段全部在 es 中保存一份
	// es 用来做搜索, 这个时候我们一般只把搜索和过滤的字段信息保存到 es 中
	// es 可以用来当 mysql 使用, 但是实际上 mysql 和 es 之间是互补的关系, 一般 mysql 用来做存储使用, es 用来做搜索使用
	// es 想要提高性能, 就要将 es 的内存设置的够大

	// 关键词搜索， 查询新品、查询热门商品、通过价格区间筛选, 通过商品分类筛选
	goodsListResponse := &proto.GoodsListResponse{}

	// match bool 复合查询
	// 1. mySQL 筛选  弊端 mySQL 负担大 改为 elastic
	// localDB := global.DB.Model(model.Goods{})
	// 2. elastic 此库不是官方库 已弃用
	//q := elastic.NewBoolQuery()
	// 3. elastic 官方库
	boolQuery := map[string]any{
		"must":     []any{},
		"must_not": []any{},
		"filter":   []any{},
		"should":   []any{},
	}

	q := map[string]any{
		"query": map[string]any{
			"bool": boolQuery,
		},
		"sort": []any{
			map[string]any{
				"_score": map[string]any{
					"order": "desc",
				},
				"id": map[string]any{
					"order": "asc", // desc 降序
				},
			},
		},
		"from": 1,
		"size": 10,
	}

	if req.KeyWords != "" {
		// 搜索
		//localDB = localDB.Where("name LIKE ?", "%"+req.KeyWords+"%")
		//q = q.Must(elastic.NewMultiMatchQuery(req.KeyWords, "name", "goods_brief"))
		multiMatchQuery := map[string]any{
			"multi_match": map[string]any{
				"query":  req.KeyWords,
				"fields": []string{"name", "goods_brief"},
			},
		}
		boolQuery["must"] = append(boolQuery["must"].([]any), multiMatchQuery)
	}
	if req.IsHot {
		//localDB = localDB.Where(model.Goods{IsHot: true})
		//q = q.Filter(elastic.NewTermQuery("is_hot", req.IsHot))
		termQuery := map[string]any{
			"term": map[string]any{
				"is_hot": req.IsHot,
			},
		}
		boolQuery["filter"] = append(boolQuery["filter"].([]any), termQuery)
	}
	if req.IsNew {
		//localDB = localDB.Where("is_new=true")
		//q = q.Filter(elastic.NewTermQuery("is_new", req.IsNew))
		termQuery := map[string]any{
			"term": map[string]any{
				"is_new": req.IsNew,
			},
		}
		boolQuery["filter"] = append(boolQuery["filter"].([]interface{}), termQuery)
	}
	if req.PriceMin > 0 {
		//localDB = localDB.Where("shop_price>=?", req.PriceMin)
		//q = q.Filter(elastic.NewRangeQuery("shop_price").Gte(req.PriceMin))
		rangeQuery := map[string]any{
			"range": map[string]any{
				"shop_price": map[string]any{
					"gte": req.PriceMin,
				},
			},
		}
		boolQuery["filter"] = append(boolQuery["filter"].([]interface{}), rangeQuery)
	}
	if req.PriceMax > 0 {
		//localDB = localDB.Where("shop_price<=?", req.PriceMax)
		//q = q.Filter(elastic.NewRangeQuery("shop_price").Lte(req.PriceMax))
		rangeQuery := map[string]any{
			"range": map[string]any{
				"shop_price": map[string]any{
					"lte": req.PriceMax,
				},
			},
		}
		boolQuery["filter"] = append(boolQuery["filter"].([]interface{}), rangeQuery)
	}
	if req.Brand > 0 {
		//localDB = localDB.Where("brand_id=?", req.Brand)
		//q = q.Filter(elastic.NewTermQuery("brand_id", req.Brand))
		termQuery := map[string]any{
			"term": map[string]any{
				"brand_id": req.Brand,
			},
		}
		boolQuery["filter"] = append(boolQuery["filter"].([]interface{}), termQuery)
	}
	// 通过 category 去查询商品
	var subQuery string
	categoryIds := make([]interface{}, 0)
	if req.TopCategory > 0 {
		var category model.Category
		if result := global.DB.First(&category, req.TopCategory); result.RowsAffected == 0 {
			return nil, status.Errorf(codes.NotFound, "商品分类不存在")
		}

		if category.Level == 1 {
			subQuery = fmt.Sprintf("select id from category where parent_category_id in (select id from category where parent_category_id=%d)", req.TopCategory)
			//global.DB.Model(&model.Category{}).Select("id").Where(fmt.Sprintf("parent_category_id in (select id from category where parent_category_id=%d)", 130358))
		} else if category.Level == 2 {
			subQuery = fmt.Sprintf("select id from category where parent_category_id=%d", req.TopCategory)
		} else if category.Level == 3 {
			subQuery = fmt.Sprintf("select id from category where id=%d", req.TopCategory)
		}

		type Result struct {
			ID int32
		}
		var result []Result
		global.DB.Model(model.Category{}).Raw(subQuery).Scan(&result)
		for _, re := range result {
			categoryIds = append(categoryIds, re.ID)
		}
		//localDB = localDB.Where(fmt.Sprintf("category_id in (%s)", subQuery))
		// 生成 terms 查询
		//q = q.Filter(elastic.NewTermsQuery("category_id", categoryIds...))
		termsQuery := map[string]any{
			"terms": map[string]any{
				"category_id": categoryIds,
			},
		}
		boolQuery["filter"] = append(boolQuery["filter"].([]interface{}), termsQuery)
	}

	// 分页
	if req.Pages == 0 {
		req.Pages = 1
	}
	switch {
	case req.PagePerNums > 100:
		req.PagePerNums = 100
	case req.PagePerNums <= 0:
		req.PagePerNums = 10
	}

	//// 1. 已弃用 SQL 筛选
	//var count int64
	//localDB.Count(&count)
	//goodsListResponse.Total = int32(count)
	//// 2. 已弃用 https://github.com/olivere/elastic
	//result, err := global.EsClient.Search().Index(model.EsGoods{}.GetIndexName()).Query(q).Sort("id", true).From(int((req.Pages - 1) * req.PagePerNums)).Size(int(req.PagePerNums)).Do(context.Background())
	//if err != nil {
	//	return nil, err
	//}
	//goodsListResponse.Total = int32(result.Hits.TotalHits.Value)
	//goodsIds := make([]string, 0)
	//for _, value := range result.Hits.Hits {
	//	good := model.EsGoods{}
	//	_ = json.Unmarshal(value.Source, &good)
	//	goodsIds = append(goodsIds, strconv.Itoa(int(good.ID)))
	//}

	q["from"] = (req.Pages - 1) * req.PagePerNums
	q["size"] = req.PagePerNums

	res, err := esapi.SearchRequest{
		Index: []string{model.EsGoods{}.GetIndexName()},
		Body:  esutil.NewJSONReader(q),
	}.Do(context.Background(), global.EsClient)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.IsError() {
		var e map[string]interface{}
		if err := json.NewDecoder(res.Body).Decode(&e); err != nil {
			zap.S().Info(err)
			return nil, err
		} else {
			zap.S().Info(
				e["error"].(map[string]interface{})["type"],
				e["error"].(map[string]interface{})["reason"])
			return nil, fmt.Errorf("[%s] %s: %s",
				res.Status(),
				e["error"].(map[string]interface{})["type"],
				e["error"].(map[string]interface{})["reason"],
			)
		}
	}

	var result map[string]interface{}
	if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
		return nil, err
	}

	hits := result["hits"].(map[string]any)
	goodsListResponse.Total = int32(hits["total"].(map[string]any)["value"].(float64))
	goodsIds := make([]string, 0)
	for _, value := range hits["hits"].([]any) {
		good := model.EsGoods{}
		marshal, err := json.Marshal(value.(map[string]any)["_source"])
		if err != nil {
			return nil, err
		}
		_ = json.Unmarshal(marshal, &good)
		goodsIds = append(goodsIds, strconv.Itoa(int(good.ID)))
	}

	if len(goodsIds) == 0 {
		return goodsListResponse, nil
	}
	// 集成 es 后 mysql 的功能变为 查询id 在 某个数组的值
	var goods []model.Goods
	//result := localDB.Preload("Category").Preload("Brands").Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&goods)
	re := global.DB.Preload("Category").Preload("Brands").Find(&goods, goodsIds)
	if re.Error != nil {
		return nil, re.Error
	}

	for _, good := range goods {
		goodsInfoResponse := ModelToResponse(good)
		goodsListResponse.Data = append(goodsListResponse.Data, &goodsInfoResponse)
	}

	return goodsListResponse, nil
}

// BatchGetGoods 用户订单有多个商品 批量查询商品信息
func (g *GoodsServer) BatchGetGoods(ctx context.Context, req *proto.BatchGoodsIdInfo) (*proto.GoodsListResponse, error) {
	goodsListResponse := &proto.GoodsListResponse{}
	var goods []model.Goods

	result := global.DB.Where(req.Id).Find(&goods)
	for _, good := range goods {
		goodsInfoResponse := ModelToResponse(good)
		goodsListResponse.Data = append(goodsListResponse.Data, &goodsInfoResponse)
	}
	goodsListResponse.Total = int32(result.RowsAffected)
	return goodsListResponse, nil
}

func (g *GoodsServer) CreateGoods(ctx context.Context, req *proto.CreateGoodsInfo) (*proto.GoodsInfoResponse, error) {
	var category model.Category
	if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品分类不存在")
	}

	var brand model.Brands
	if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "品牌不存在")
	}
	//这里没有看到图片文件是如何上传， 在微服务中 普通的文件上传已经不再使用
	goods := model.Goods{
		Brands:          brand,
		BrandsID:        brand.ID,
		Category:        category,
		CategoryID:      category.ID,
		Name:            req.Name,
		GoodsSn:         req.GoodsSn,
		MarketPrice:     req.MarketPrice,
		ShopPrice:       req.ShopPrice,
		GoodsBrief:      req.GoodsBrief,
		ShipFree:        req.ShipFree,
		Images:          req.Images,
		DescImages:      req.DescImages,
		GoodsFrontImage: req.GoodsFrontImage,
		IsNew:           req.IsNew,
		IsHot:           req.IsHot,
		OnSale:          req.OnSale,
	}
	// gorm 已经做好 钩子 的事务回滚问题了
	//tx := global.DB.Begin()
	result := global.DB.Save(&goods)
	if result.Error != nil {
		//global.DB.Rollback()
		return nil, result.Error
	}
	//tx.Commit()
	return &proto.GoodsInfoResponse{
		Id: goods.ID,
	}, nil
}

func (g *GoodsServer) DeleteGoods(ctx context.Context, req *proto.DeleteGoodsInfo) (*emptypb.Empty, error) {
	if result := global.DB.Delete(&model.Goods{BaseModel: model.BaseModel{ID: req.Id}}, req.Id); result.Error != nil {
		return nil, result.Error
	}
	return &emptypb.Empty{}, nil
}

func (g *GoodsServer) UpdateGoods(ctx context.Context, req *proto.CreateGoodsInfo) (*emptypb.Empty, error) {
	var goods model.Goods

	if result := global.DB.First(&goods, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}

	if req.BrandId == 0 {
		goods.IsNew = req.IsNew
		goods.IsHot = req.IsHot
		goods.OnSale = req.OnSale
	} else {
		var category model.Category
		if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
			return nil, status.Errorf(codes.NotFound, "商品分类不存在")
		}

		var brand model.Brands
		if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
			return nil, status.Errorf(codes.NotFound, "品牌不存在")
		}

		goods.Brands = brand
		goods.Category = category
		goods.Name = req.Name
		goods.GoodsSn = req.GoodsSn
		goods.MarketPrice = req.MarketPrice
		goods.ShopPrice = req.ShopPrice
		goods.GoodsBrief = req.GoodsBrief
		goods.ShipFree = req.ShipFree
		goods.Images = req.Images
		goods.DescImages = req.DescImages
		goods.GoodsFrontImage = req.GoodsFrontImage
	}
	result := global.DB.Save(&goods)
	if result.Error != nil {
		return nil, result.Error
	}
	return &emptypb.Empty{}, nil
}

func (g *GoodsServer) GetGoodsDetail(ctx context.Context, req *proto.GoodInfoRequest) (*proto.GoodsInfoResponse, error) {
	var goods model.Goods

	if result := global.DB.Preload("Category").Preload("Brands").First(&goods, req.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}

	goodsInfoResponse := ModelToResponse(goods)
	return &goodsInfoResponse, nil
}
